package cn.epsilon3.aop;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.epsilon3.annotation.E3Log;
import cn.epsilon3.constant.Base;
import cn.epsilon3.constant.LogStatusEnum;
import cn.epsilon3.factory.E3ScheduledFactory;
import cn.epsilon3.scheduled.E3AsyncScheduled;
import cn.epsilon3.utils.E3JsonKit;
import cn.epsilon3.utils.HttpUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * @description 日志切面
 *
 * @author: epsilon3
 * @date: 2023/9/5 23:12
 */
@Aspect
@Component
public class E3LogAop {

    /**
     * 执行时间
     */
    private static final ThreadLocal<Long> USE_TIME = new NamedThreadLocal<Long>("USE_TIME");

    /**
     * 切点
     */
    @Pointcut("@annotation(cn.epsilon3.annotation.E3Log)")
    private void aspect() {
    }

    /**
     * 方法执行前记录时间
     *
     * @param point
     */
    @Before(value = "aspect()")
    public void before(JoinPoint point) {
        USE_TIME.set(System.currentTimeMillis());
    }

    /**
     * 方法执行完毕后记录日志
     *
     * @param point
     * @param result
     */
    @AfterReturning(pointcut = "aspect()",returning = "result")
    public void afterReturning(JoinPoint point,Object result) {
        saveLog(point, result,null);
    }

    /**
     * 方法执行完毕后记录日志
     *
     * @param point
     * @param e
     */
    @AfterThrowing(pointcut = "aspect()",throwing = "e")
    public void around(JoinPoint point,Exception e) {
        saveLog(point, null,e);
    }

    /**
     * 日志解析存储
     *
     * @param point
     * @param result
     */
    private void saveLog(JoinPoint point, Object result,Exception e) {
        String methodName = point.getSignature().getName();
        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        boolean e3LogPresent = method.isAnnotationPresent(E3Log.class);
        if (e3LogPresent) {
            E3Log e3Log = method.getAnnotation(E3Log.class);
            String recordModule = e3Log.recordModule();
            String recordName = e3Log.recordName();
            if (StrUtil.isBlank(recordName)) {
                boolean apiOperationPresent = method.isAnnotationPresent(ApiOperation.class);
                if (apiOperationPresent) {
                    ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
                    recordName = apiOperation.value();
                }
            }
            if (StrUtil.isBlank(recordModule)) {
                Class<?> aClass = point.getTarget().getClass();
                boolean apiPresent = aClass.isAnnotationPresent(Api.class);
                if (apiPresent) {
                    Api api = aClass.getAnnotation(Api.class);
                    recordModule = api.tags()[Base.INT0];
                }
            }
            boolean recordEnable = e3Log.recordEnable();
            boolean recordArgs = e3Log.recordArgs();
            boolean recordResult = e3Log.recordResult();
            boolean recordGet = e3Log.recordGet();
            cn.epsilon3.domain.e3.po.E3Log recordLog = new cn.epsilon3.domain.e3.po.E3Log();
            if (recordEnable) {
                boolean getMapping = method.isAnnotationPresent(GetMapping.class);
                boolean requestMapping = method.isAnnotationPresent(RequestMapping.class);
                boolean requestGetMapping = false;
                if (requestMapping) {
                    RequestMapping mapping = method.getAnnotation(RequestMapping.class);
                    RequestMethod[] requestMethods = mapping.method();
                    RequestMethod requestMethod = requestMethods[Base.INT0];
                    if (requestMethod.equals(RequestMethod.GET)) {
                        requestGetMapping = true;
                    }
                }
                if (!getMapping || !requestGetMapping || recordGet) {
                    recordLog.setRecordTime(new Date());
                    recordLog.setRecordModule(recordModule);
                    recordLog.setRecordName(recordName);
                    recordLog.setRecordMethod(methodName);
                    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                            .getRequest();
                    String ip = HttpUtils.getIp(request);
                    recordLog.setRecordIp(ip);
                    recordLog.setRecordUrl(request.getRequestURI());
                    if (recordArgs) {
                        Object[] args = point.getArgs();
                        recordLog.setRecordArgs(E3JsonKit.obj2JsonStr(args));
                    }
                    if (recordResult) {
                        if (ObjectUtil.isNull(e)) {
                            recordLog.setRecordResult(E3JsonKit.obj2JsonStr(result));
                        } else {
                            recordLog.setRecordStatus(LogStatusEnum.EX.getCode());
                            recordLog.setRecordException(e.getMessage());
                        }
                    }
                    recordLog.setRecordUseTime(System.currentTimeMillis() - USE_TIME.get());
                    USE_TIME.remove();
                    E3AsyncScheduled.in().start(E3ScheduledFactory.recordLog(recordLog));
                }
            }
        }
    }
}
